package academic.emailClient.model.entities;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.UUID;
import java.util.logging.Logger;

import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.log4j.helpers.DateTimeDateFormat;

import org.apache.commons.lang.time.DateUtils;

import academic.emailClient.security.model.User;
import academic.emailClient.security.persistence.UserDAO;
import academic.emailClient.security.persistence.UserDAOMock;
import academic.emailServer.EmailServer;

/**
 * Responsible for merging 2 meetings according to some criteria defined by the email applications
 * @author r_sanc
 *
 */
public class MeetingMerger {
	//Original meeting
	private Meeting originalMeeting;
	//Meeting with modifications
	private Meeting updatedMeeting;
	//Meetings of the email client
	private HashMap<UUID, Meeting> meetings;
	//List of attributes in conflict
	private ArrayList<String> attributesInConflict;
	//Logger
	static Logger logger = Logger.getLogger(MeetingMerger.class.getClass().getName());
	
	/**
	 * Constructor of the merger
	 * @param originalMeeting
	 * @param updatedMeeting
	 * @param meetings
	 */
	public MeetingMerger(Meeting originalMeeting, Meeting updatedMeeting,
			HashMap<UUID, Meeting> meetings) {
		super();
		this.originalMeeting = originalMeeting;		
		this.updatedMeeting = updatedMeeting;
		this.meetings = meetings;
		this.attributesInConflict = new ArrayList<String>();
	}
	

	/**
	 * Merge each attribute of meetings received
	 * @return Meeting with merged changes
	 */
	public Meeting mergeMeetings(){
		mergePurpose();
		mergeLocation();
		mergeDescription();
		mergeAttendees();
		mergeMeetingSchedule();
		return updatedMeeting;
	}
	
	public Meeting getMergeResult(){
		return updatedMeeting;
	}
	
	/**
	 * Returns list of conflicts found
	 * @return Conflicts
	 */
	public ArrayList<String> getConflictsList(){
		return attributesInConflict;
	}
	
	/**
	 * Notifies if merging meeting, got conflicts
	 * @return True if conflicts are found
	 */
	public boolean mergeHasConflicts(){
		return attributesInConflict.size() > 0;
	}
	
	/**
	 * Merges meetings location
	 * If location is the same it won't change anything
	 * If one of the location contains the other, it will save the one that contains the other
	 * If locations can't be merged,  it will save a conflict
	 */
	private void mergeLocation(){
		String originalLocation = originalMeeting.getLocation();
		String updatedLocation = updatedMeeting.getLocation();
		String mergedLocation = mergeText(originalLocation, updatedLocation, MeetingAttributes.LOCATION.name());
		updatedMeeting.setLocation(mergedLocation);
	}
	
	/**
	 * Merges meetings purpose
	 * If purpose is the same it won't change anything
	 * If one of the purpose contains the other, it will save the one that contains the other
	 * If purposes can't be merged,  it will save a conflict
	 */
	private void mergePurpose(){
		String originalPurpose = originalMeeting.getPurpose();
		String updatedPurpose = updatedMeeting.getPurpose();
		String mergedPurpose = mergeText(originalPurpose, updatedPurpose, MeetingAttributes.PURPOSE.name());
		updatedMeeting.setPurpose(mergedPurpose);
	}

	/**
	 * Merges meetings description
	 * If description is the same it won't change anything
	 * If one of the description contains the other, it will save the one that contains the other
	 * If descriptions can't be merged,  it will save a conflict
	 */
	private void mergeDescription(){		
		String originalDescription = originalMeeting.getDescription();
		String updatedDescription = updatedMeeting.getDescription();
		String mergedDescription = mergeText(originalDescription, updatedDescription, MeetingAttributes.DESCRIPTION.name());
		updatedMeeting.setDescription(mergedDescription);
	}	
	
	/**
	 * Determines which date (original or updated) is the one that is going to be taken depending on next criteria. 
	 * if start dates are the same and duration changed, accepted duration is the lowest of the two
	 * if updated meeting date is overlaping with another when one of the attendees is assisting, selected date will be the one of the original meeting
	 * Decisions taken are saved in updatedMeeting
	 */
	public void mergeMeetingSchedule(){	
		Date originalDate = originalMeeting.getStart();
		Date modifiedDate = updatedMeeting.getStart();
		
		logger.info("original date : " + originalDate.toString());
		logger.info("modified date: " + modifiedDate.toString());
		
		//Validates if date didn't change
		if (originalDate.equals(modifiedDate)) {
			logger.info("Dates are the same, validates duration");
			logger.info("original duration: " + originalMeeting.getDuration());
			logger.info("update duration: " + updatedMeeting.getDuration());
			
			//Validates meeting duration was reduced, so all attendees will be able to go
			if (originalMeeting.getDuration() < updatedMeeting.getDuration())
				updatedMeeting.setDuration(originalMeeting.getDuration());
		}
		else {
			//Validates if attendees are able to go to the meeting
			for (Meeting meeting : meetings.values()){
				
				//Don't validate availability of the same meeting
				if (meeting.getId().equals(originalMeeting.getId()))
					continue;
				
				logger.info("Validating meeting: " + meeting.getId().toString());
				logger.info("Updated meeting schedule: " + updatedMeeting.getStart().toString());
				logger.info("Created meeting schedule: " + meeting.getStart().toString());
				
				//Validates if meetings have overlaped schedule, if does it, have to validate attendees
				if (!meeting.haveOverlapedSchedules(updatedMeeting))
					continue;
				
				logger.info("Meeting is overlaping");
				@SuppressWarnings("unchecked")
				ArrayList<String> attendees = (ArrayList<String>) updatedMeeting.getAttendees().clone();
				User owner = new UserDAOMock().getUser(updatedMeeting.getOwner());				
				attendees.add(owner.getFullname());
				
				//Validates if some of the attendees is busy for the new date
				for (String attendee : attendees){
					
					//if user is busy, date should be remain as the original dae
					if (meeting.isUserAttending(attendee)) {
						logger.info("Attendee " + attendee + " is also asisting");
						updatedMeeting.setStart(originalMeeting.getStart());
						updatedMeeting.setDuration(originalMeeting.getDuration());
						return;
					}
				}			
			}	
		}
	}
	
	/**
	 * Merges attendees of the original meeting and the updated in the updated meeting
	 */
	private void mergeAttendees(){
		for (String originalAttendee : originalMeeting.getAttendees()){
			if (!updatedMeeting.isUserAttending(originalAttendee)){
				updatedMeeting.addAttendee(originalAttendee);
			}
		}
	}
	
	/**
	 * Merge texts if it can, if it doesn't, will save the conflict in an array, so the user can know about it
	 * @param originalText
	 * @param updatedText
	 * @param field
	 * @return
	 */
	private String mergeText(String originalText, String updatedText, String field){
		logger.info("Merging " + field );
		logger.info("Original Text " + originalText);
		logger.info("Updated Text " + updatedText);
		//If texts are the same one of the texts is returned
		if (areSameTexts(originalText, updatedText))
			return originalText;
		
		//If texts can be merged, it will return the result of the merging
		if (areTextsMergeable(originalText, updatedText))
			return mergeText(originalText, updatedText);
		
		//If text can not be merged, conflicts have to be resolved by user
		attributesInConflict.add(field);
		return updatedText;
	}

	/**
	 * Merge text according to the  next criteria, if one of the texts contains the other, it will 
	 * return the one that is containing the other 
	 * @param originalText 
	 * @param updatedText 
	 * @return merged text
	 */
	private String mergeText(String originalText, String updatedText){	
		//Validates if updated text has more information
		if (updatedText.contains(originalText)){
			return updatedText;
		}		
		//Validates if original text has more information
		else{
			return originalText;
		}
	}
	
	/**
	 * Validates if compared text are the same
	 * @param originalText
	 * @param updatedText
	 * @return True if both texts are the same
	 */
	private boolean areSameTexts(String originalText, String updatedText){
		//Validates if texts are equal
		return (originalText.equals(updatedText));
	}
	
	/**
	 * Validate if both text can be merged, choosing the text that contains the other
	 * @param originalText
	 * @param updatedText
	 * @return
	 */
	private boolean areTextsMergeable(String originalText, String updatedText){		
		//Validates if updated text has more information		
		if (updatedText.contains(originalText)){
			return true;
		}
		
		//Validates if original text has more information
		if (originalText.contains(updatedText)){
			return true;
		}		
		return false;
	}

}
